/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *     PARC     initial implementation
 * ******************************************************************/

package org.aspectj.weaver.bcel;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;

import org.aspectj.weaver.AdviceKind;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.CrosscuttingMembers;
import org.aspectj.weaver.MemberImpl;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.ShadowMunger;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.patterns.ConstantPoolSimulator;
import org.aspectj.weaver.patterns.Pointcut;
import org.aspectj.weaver.patterns.SimpleScope;

public class PointcutResidueTestCase extends WeaveTestCase {
	{
		regenerate = false;
	}

	public PointcutResidueTestCase(String name) {
		super(name);
	}

	String[] none = new String[0];

	// -----

	// ----

	public void testArgResidue1() throws IOException {
		checkMultiArgWeave("StringResidue1",
				"call(* *(java.lang.Object, java.lang.Object)) && args(java.lang.String, java.lang.String)");
	}

	public void testArgResidue2() throws IOException {
		checkMultiArgWeave("StringResidue2", "call(* *(java.lang.Object, java.lang.Object)) && args(.., java.lang.String)");
	}

	public void testArgResidue3() throws IOException {
		checkMultiArgWeave("StringResidue3", "call(* *(java.lang.Object, java.lang.Object)) && args(java.lang.String, ..)");
	}

	// BETAX this is a beta feature.
	// public void testArgResidue4() throws IOException {
	// checkMultiArgWeave(
	// "StringResidue4",
	// "call(* *(java.lang.Object, java.lang.Object)) && args(.., java.lang.String, ..)");
	// }

	public void testMultiArgState() throws IOException {
		checkWeave("StateResidue", "MultiArgHelloWorld", "call(* *(java.lang.Object, java.lang.Object)) && args(s, ..)",
				new String[] { "java.lang.String" }, new String[] { "s" });
		checkWeave("StateResidue", "MultiArgHelloWorld", "call(* *(java.lang.Object, java.lang.Object)) && args(s, *)",
				new String[] { "java.lang.String" }, new String[] { "s" });
	}

	public void testAdd() throws IOException {
		checkDynamicWeave("AddResidue", "call(public * add(..)) && target(java.util.ArrayList)");
		checkDynamicWeave("AddResidue", "call(public * add(..)) && (target(java.util.ArrayList) || target(java.lang.String))");
		checkDynamicWeave("AddResidue",
				"call(public * add(..)) && this(java.io.Serializable) && target(java.util.ArrayList) && !this(java.lang.Integer)");
	}

	public void testNot() throws IOException {
		checkDynamicWeave("AddNotResidue", "call(public * add(..)) && !target(java.util.ArrayList)");
		checkDynamicWeave("AddNotResidue", "call(public * add(..)) && !(target(java.util.ArrayList) || target(java.lang.String)) ");
		checkDynamicWeave("AddNotResidue", "call(public * add(..)) && target(java.lang.Object) && !target(java.util.ArrayList)");
	}

	public void testState() throws IOException {
		checkWeave("AddStateResidue", "DynamicHelloWorld", "call(public * add(..)) && target(list)",
				new String[] { "java.util.ArrayList" }, new String[] { "list" });
		checkWeave("AddStateResidue", "DynamicHelloWorld", "target(foo) && !target(java.lang.Integer) && call(public * add(..))",
				new String[] { "java.util.ArrayList" }, new String[] { "foo" });
		checkDynamicWeave("AddResidue", "call(public * add(..)) && (target(java.util.ArrayList) || target(java.lang.String))");
		checkDynamicWeave("AddResidue",
				"call(public * add(..)) && this(java.io.Serializable) && target(java.util.ArrayList) && !this(java.lang.Integer)");
	}

	public void testNoResidueArgs() throws IOException {
		checkDynamicWeave("NoResidue", "call(public * add(..)) && args(java.lang.Object)");
		checkDynamicWeave("NoResidue", "call(public * add(..)) && args(*)");
		checkDynamicWeave("NoResidue", "call(public * add(..))");
	}

	// ---- cflow tests

	public void testCflowState() throws IOException {
		checkWeave("CflowStateResidue", "DynamicHelloWorld",
				"cflow(call(public * add(..)) && target(list)) && execution(public void main(..))",
				new String[] { "java.util.ArrayList" }, new String[] { "list" });
		// checkWeave(
		// "CflowStateResidue",
		// "DynamicHelloWorld",
		// "cflow(call(public * add(..)) && target(list)) && this(obj) && execution(public void doit(..))",
		// new String[] { "java.lang.Object", "java.util.ArrayList" },
		// new String[] { "obj", "list" });
		// checkWeave(
		// "AddStateResidue",
		// "DynamicHelloWorld",
		// "target(foo) && !target(java.lang.Integer) && call(public * add(..))",
		// new String[] { "java.util.ArrayList" },
		// new String[] { "foo" });
		// checkDynamicWeave(
		// "AddResidue",
		// "call(public * add(..)) && (target(java.util.ArrayList) || target(java.lang.String))");
		// checkDynamicWeave(
		// "AddResidue",
		// "call(public * add(..)) && this(java.io.Serializable) && target(java.util.ArrayList) && !this(java.lang.Integer)");
	}

	// ----

	private void checkDynamicWeave(String label, String pointcutSource) throws IOException {
		checkWeave(label, "DynamicHelloWorld", pointcutSource, new String[0], new String[0]);
	}

	private void checkMultiArgWeave(String label, String pointcutSource) throws IOException {
		checkWeave(label, "MultiArgHelloWorld", pointcutSource, new String[0], new String[0]);
	}

	private void checkWeave(String label, String filename, String pointcutSource, String[] formalTypes, String[] formalNames)
			throws IOException {
		final Pointcut sp = Pointcut.fromString(pointcutSource);
		final Pointcut rp = sp.resolve(new SimpleScope(world, SimpleScope.makeFormalBindings(UnresolvedType.forNames(formalTypes),
				formalNames)));

		ShadowMunger pp = new BcelAdvice(AdviceKind.Before, rp, MemberImpl.method(UnresolvedType.forName("Aspect"),
				Modifier.STATIC, "ajc_before_0",
				MemberImpl.typesToSignature(UnresolvedType.VOID, UnresolvedType.forNames(formalTypes), false)), 0, -1, -1, null,
				null);

		ResolvedType inAspect = world.resolve("Aspect");
		CrosscuttingMembers xcut = new CrosscuttingMembers(inAspect, true);
		inAspect.crosscuttingMembers = xcut;

		ShadowMunger cp = pp.concretize(inAspect, world, null);

		xcut.addConcreteShadowMunger(cp);

		// System.out.println("extras: " + inAspect.getExtraConcreteShadowMungers());
		// List advice = new ArrayList();
		// advice.add(cp);
		// advice.addAll(inAspect.getExtraConcreteShadowMungers());
		weaveTest(new String[] { filename }, label, xcut.getShadowMungers());

		checkSerialize(rp);
	}

	public void weaveTest(String name, String outName, ShadowMunger planner) throws IOException {
		List<ShadowMunger> l = Collections.singletonList(planner);
		weaveTest(name, outName, l);
	}

	public void checkSerialize(Pointcut p) throws IOException {
		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		ConstantPoolSimulator cps = new ConstantPoolSimulator();
		CompressingDataOutputStream out = new CompressingDataOutputStream(bo, cps);
		p.write(out);
		out.close();

		ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
		VersionedDataInputStream in = new VersionedDataInputStream(bi, cps);
		Pointcut newP = Pointcut.read(in, null);

		assertEquals("write/read", p, newP);
	}

}
